
#include "sea.h"
#include <iostream>
#include "freefuncs.h"
#ifdef VC110
#include <thread>
#endif


SEA::SEA(const EightLayerIM& elim) :populationSize(-1), selectedSize(-1), tournamentSize(-1), indSizeLimit(-1), eliteSize(-1), diversitySize(-1), 
			nBRadius(-1), historySize(-1), firstLayer(-1), lastLayer(-1), maxIteration(-1), highestFitnessSoFar(-1)
{
	picToBuild = elim;
}


bool SEA::isFitter (int i, int j) const
{
	if (indFitnessize[i].fitness > indFitnessize[j].fitness ||
		(indFitnessize[i].fitness == indFitnessize[j].fitness && indFitnessize[i].infoSize < indFitnessize[j].infoSize) ||
		(indFitnessize[i].fitness == indFitnessize[j].fitness && indFitnessize[i].infoSize == indFitnessize[j].infoSize && indFitnessize[i].ruleNum < indFitnessize[j].ruleNum)  )
		return true;
	else
		return false;
}

bool SEA::equalyFit (int i, int j) const
{
	if (indFitnessize[i].fitness == indFitnessize[j].fitness && indFitnessize[i].infoSize == indFitnessize[j].infoSize && indFitnessize[i].ruleNum == indFitnessize[j].ruleNum)
		return true;
	else
		return false;
}

bool SEA::isElite(int e) const
{
	for (int i = 0; i < (int) eliteIndices.size(); ++i)
		if (eliteIndices[i] == e)
			return true;
	return false;
}


void SEA::initiatePool()
{
	this->indSizeLimit = 1;
	SEARuleSet rs(nBRadius, historySize);
	for (int i = 0; i < populationSize; ++i)
	{
		rs.initiateRandom(indSizeLimit);
		individuals.push_back(rs);
	}
}

void SEA::updateSizeLimit()
{
	++indSizeLimit;
}

void SEA::chooseSelected()
{
	selectedIndices.clear();
	int* tournamentIndIndex = new int[tournamentSize];	//the indices of the individuals in the tournament
	selectedIndices.clear();
	int tournamentWinnerIndex;
	for (int i = 0; i < selectedSize; ++i)
	{
		for (int t = 0; t < tournamentSize; ++t)
			tournamentIndIndex[t] = rand() % populationSize;
		tournamentWinnerIndex = 0;
		for (int t = 1; t < tournamentSize; ++t)
			if (isFitter(tournamentIndIndex[t], tournamentIndIndex[tournamentWinnerIndex]))
				tournamentWinnerIndex = t;
		//now the tournamentWinnerIndex has the valid index. i.e. the best in the tournament.
		//note that the index stored in tournamentWinnerIndex is for the tournamentIndIndex, and not individuals.
		this->selectedIndices.push_back(tournamentIndIndex[tournamentWinnerIndex]);
	}
	delete[] tournamentIndIndex;
}

void SEA::mutateSelectedAddToPool()
{
	SEARuleSet mutant(nBRadius, historySize);
	for (int i = 0; i < selectedSize; ++i)
	{
		mutant = individuals[selectedIndices[i]];
		mutant.mutate();
		individuals.push_back(mutant);
	}
}

int  SEA::xOverSelectedAddToPool()
{
	int result = 0;
	SEARuleSet child(nBRadius, historySize);
	for (int p1 = 0; p1 < selectedSize; ++p1)
		for (int p2 = p1+1; p2 < selectedSize; ++p2)
		{
			child.xOver(individuals[p1], individuals[p2]);
			//xOver does not reduce the size, the size of the child is always more than of its parents.
			if (child.getRuleNum() <= indSizeLimit &&
				child.fitness(picToBuild, firstLayer, lastLayer) > individuals[p1].fitness(picToBuild, firstLayer, lastLayer) &&
				child.fitness(picToBuild, firstLayer, lastLayer) > individuals[p2].fitness(picToBuild, firstLayer, lastLayer) )
			{
				++result;
				individuals.push_back(child);
			}
		}
		return result;
}

void SEA::diversify()
{
	SEARuleSet rs(nBRadius, historySize);
	for (int i = 0; i < diversitySize; ++i)
	{
		rs.initiateRandom(1);
		individuals.push_back(rs);
	}
}

void SEA::killExtras()
{
	int victimIndex;
	while ((int) individuals.size() > populationSize)
	{
		victimIndex = rand() % (int)(individuals.size() - eliteIndices.size()); //it ignores the elites (jumps over them in the following code)
		for (int i = 0; i < (int) eliteIndices.size(); ++i)
			if (eliteIndices[i] <= victimIndex)
				++victimIndex;
		if (isElite(victimIndex))
			cerr << "Shit happened. elites cannot be killed.";
		individuals.erase(individuals.begin()+victimIndex);
	}
}

void SEA::updateBestIndividuals()
{
	int bestSoFar = 0;
	eliteIndices.clear();
	eliteIndices.push_back(bestSoFar);
	for (int i = 1; i < (int) individuals.size(); ++i)
	{
		if (isFitter(i, bestSoFar))
		{
			eliteIndices.clear();
			eliteIndices.push_back(i);
			bestSoFar = i;
		}
		else if (equalyFit(i,bestSoFar))
		{
			eliteIndices.push_back(i);
		}
	}
	//highestFitnessSoFar = individuals[eliteIndices[0]].fitness(picToBuild, firstLayer, lastLayer);
	highestFitnessSoFar = indFitnessize[eliteIndices[0]].fitness;
}

void SEA::updateFitnessizes()
{
	indFitnessize.clear();
	fitnessize toBeAdded;
	for (int i = 0; i < (int)individuals.size(); ++i)
	{
		toBeAdded.fitness = individuals[i].fitness(picToBuild, firstLayer, lastLayer);
		toBeAdded.ruleNum = individuals[i].getRuleNum();
		toBeAdded.infoSize = individuals[i].getNetInfoSizeInBits();
		indFitnessize.push_back(toBeAdded);
	}
}

#ifdef VC110
void SEA::updateFitnessizes_MultiThread()
{
	indFitnessize.clear();
	indFitnessize.resize(individuals.size());
	int maxThreadCount = std::thread::hardware_concurrency();
	std::vector<std::thread> threads;
	std::vector<int> borderIndices; //the indices of the "block separators" of the individuals. If we have 11 inds and 4 threads, these indices are 0, 3, 6, 10 (the last one is larger)
	int borderModule = ((int) individuals.size() / maxThreadCount) + 1; //
	for (int i = 0; i < maxThreadCount; ++i)
		borderIndices.push_back(i * borderModule);
	borderIndices.push_back(individuals.size());

	int startRange, endRange;
	//EightLayerIM* picCopy;
	//std::thread t(std::bind(&SEA::updateFitnessInBlock, this, 0, 1, picCopy));
	//std::thread s(std::bind(&SEA::updateFitnessInBlock, this, 2, 3, picCopy));
	
	for (int i = 0; i < maxThreadCount; ++i)
	{
		startRange = borderIndices[i];
		endRange = borderIndices[i+1];
		//picCopy = new EightLayerIM(picToBuild);
		EightLayerIM picCopy(picToBuild);
		//TODO: a function that inputs a range of individuals and updates the fitnessizes.		
		threads.push_back(std::thread(std::bind(&SEA::updateFitnessInBlock, this, startRange, endRange, picCopy)));
	}

	for (int i = 0; i < (int)threads.size(); ++i)
		threads[i].join();
	
}


void SEA::updateFitnessInBlock(int firstIndIndex, int lastIndIndex, EightLayerIM& picCopy)
{
	//cout << "Updating inds from " << firstIndIndex << " to " << lastIndIndex << " in a thread." << endl;
	for (int i = firstIndIndex; i < lastIndIndex; ++i)
	{
		indFitnessize[i].fitness = individuals[i].fitness(picCopy, firstLayer, lastLayer);
		indFitnessize[i].ruleNum = individuals[i].getRuleNum();
		indFitnessize[i].infoSize = individuals[i].getNetInfoSizeInBits();
	}
}
#endif
bool SEA::checkDataMembersAreSet () const
{
	if (maxIteration == -1) 
	{
		cerr << "Maximum number of iterations is not set.";
		return false;
	}
	if (populationSize == -1) 
	{
		cerr << "Population Size not set.";
		return false;
	}
	if (selectedSize == -1) 
	{
		cerr << "Size of selected individuals is not set.";
		return false;
	}
	if (tournamentSize == -1)
	{
		cerr << "Tournament size not set.";
		return false;
	}
	if (indSizeLimit == -1)
	{
		cerr << "Individuals' size limit is not set.";
		return false;
	}
	if (eliteSize == -1)
	{
		cerr << "Number of elites is not set.";
		return false;
	}
	if (diversitySize ==-1)
	{
		cerr << "Diversity size not set.";
		return false;
	}
	if (nBRadius == -1)
	{
		cerr << "Neighborhood radius is not set.";
		return false;
	}
	if (historySize == -1)
	{
		cerr << "History size is not set.";
		return false;
	}
	if (firstLayer == -1)
	{
		cerr << "First Layer Index is not set.";
		return false;
	}
	if (lastLayer==-1)
	{
		cerr << "Last Layer Index is not set.";
		return false;
	}

	return true;
}

void SEA::run (bool onlineReport) 
{
	if (outputFolder.length() < 1)
		cerr << "ERROR: Output folder is not set.\n";
	string fitnessFileName  (outputFolder + "sea_fitnesses_4.txt");
	string ruleNumsFileName (outputFolder + "sea_indrulenums_4.txt");
	string rawSizesFileName (outputFolder + "sea_indconditionalparts_4.txt");
	string selectedFileName (outputFolder + "sea_selectedinds_4.txt");
	string elitesFileName   (outputFolder + "sea_eliteinds_4.txt");

	ofstream fitnessFile  (fitnessFileName.data());
	ofstream ruleNumsFile (ruleNumsFileName.data());
	ofstream rawSizesFile (rawSizesFileName.data());
	ofstream selectedFile (selectedFileName.data());
	ofstream elitesFile   (elitesFileName.data());
	if (checkDataMembersAreSet() == false)
		return;

	initiatePool();
	
	logFile << "Initial population of " << populationSize << " created.\n";
	int maxFitness = 0;
#ifdef FITNESS_ALL_PIXELS
	maxFitness = picToBuild.getRowNum() * picToBuild.getColNum();
#else
	maxFitness = FITNESS_SAMPLE_PIXELS;
#endif

	logFile << "Maximum possible value for fitness: " << maxFitness << endl;
	
	int xOverChildrenNum;
	int generation;
	for (generation = 0; generation < maxIteration; ++generation)
	{
		if (onlineReport == true)
			cout << generation << "- ";
		logFile << "Generation " << generation << endl;
		updateSizeLimit();
		
#ifdef VC110
		updateFitnessizes_MultiThread();
#else
		updateFitnessizes(); 
#endif

		printFitnesses(fitnessFile);
		printRuleNums(ruleNumsFile);
		printInfoSizes(rawSizesFile);

		chooseSelected();

		printSelecteds(selectedFile);
		mutateSelectedAddToPool();
		xOverChildrenNum = xOverSelectedAddToPool();
		logFile << xOverChildrenNum << " children were added to the pool after x-over.\n";
		diversify();
#ifdef VC110
		updateFitnessizes_MultiThread();
#else
		updateFitnessizes(); 
#endif
		updateBestIndividuals();
		printElites(elitesFile);
		logFile << "The highest fitness in the population after mutation, xover and diversify: " << highestFitnessSoFar;
		logFile << " (owned by " << eliteIndices.size() << " individuals):\n";
		for (int i = 0; i < (int) eliteIndices.size(); ++i)
			logFile << "Elite " << i << ":\n" << individuals[eliteIndices[i]] << endl;
		if (highestFitnessSoFar == maxFitness)
			break;
		killExtras();
		logFile << "Population was resized back to " << populationSize << " individuals.\n";
		if (onlineReport == true)
			cout << highestFitnessSoFar << endl;
	}
	logFile << "Evolution finished after " << generation - 1 << " generations.\n";
}

void SEA::setOutputFolder(string folderName)
{
	this->outputFolder = folderName;
}

void SEA::setLoggerFile (string fileName) 
{
	string logFileName(outputFolder + fileName);
	logFile = ofstream(logFileName.data());
}

void SEA::printFitnesses(ofstream& file) const
{
	for (int i = 0; i < (int) indFitnessize.size(); ++i)
		file << indFitnessize[i].fitness << ",";
	file << endl;
}

void SEA::printInfoSizes(ofstream& file) const
{
	for (int i = 0; i < (int) indFitnessize.size(); ++i)
		file << indFitnessize[i].infoSize << ",";
	file << endl;
}

void SEA::printRuleNums(ofstream& file) const
{
	for (int i = 0; i < (int) indFitnessize.size(); ++i)
		file << indFitnessize[i].ruleNum << ",";
	file << endl;
}

void SEA::printSelecteds(ofstream& file) const
{
	for (int i = 0; i < (int) this->selectedIndices.size(); ++i)
		file << selectedIndices[i] << ",";
	file << endl;
}

void SEA::printElites(ofstream& file) const
{
	for (int i = 0; i < (int) this->eliteIndices.size(); ++i)
		file << eliteIndices[i] << ",";
	file << endl;
}

SEA::SEA(const SEA& other)
{
	std::cerr << "CC was called!!!" << endl;
}